﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Scripting.Hosting;

namespace InstantSpine.Core
{
    class PythonScriptCompiler
    {
        /// <summary>
        /// Engine used to compile and execute scripts describing dependencies between
        /// model parameters in a user-defined function "update_parameters"
        /// </summary>
        ScriptEngine ironPythonEngine;

        /// <summary>
        /// Lazily intializes the IronPython script engine
        /// </summary>
        void InitScriptEngine() {
            if (ironPythonEngine == null) {
                Debug.WriteLine("Initializing IronPython engine");
                ironPythonEngine = IronPython.Hosting.Python.CreateEngine();
            }
        }

        /// <summary>
        /// Loads and compiles a Python script and, on success, assigns it as
        /// the default parameter computer
        /// </summary>
        /// <param name="scriptfilename">The script file to be compiled</param>
        /// <param name="stat">Callback used to inform the caller about the current status</param>
        /// <returns>Information about the success of the compilation attempt</returns>
        public T LoadAndCompile<T>(string scriptfilename, string resultVarName, Action<string> stat) {
            // lazily initialize the IronPython engine
            stat(Properties.Resources.InitPythonRT);
            InitScriptEngine();

            // load the script and try to compile
            stat(Properties.Resources.CompilingScript);
            var source = ironPythonEngine.CreateScriptSourceFromFile(scriptfilename);
            try {
                var compiledSource = source.Compile();
                compiledSource.Execute(); // evaluated script

                // try to retrieve a function/object/var named resultVarName
                return compiledSource
                       .DefaultScope
                       .GetVariable<T>(resultVarName);
            }
            // Script contains invalid code                
            catch (Microsoft.Scripting.SyntaxErrorException synEx) {
                var errorMessage = 
                    string.Format(Properties.Resources.ScriptSyntaxError,
                                  synEx.GetSymbolDocumentName(),
                                  synEx.RawSpan.Start.Line,
                                  synEx.RawSpan.Start.Column)
                    + Environment.NewLine
                    + Environment.NewLine
                    + synEx.GetCodeLine()
                    + Environment.NewLine
                    + Environment.NewLine
                    + synEx.Message;

                throw new CompilationException(
                    Properties.Resources.ScriptSyntaxErrorCaption, 
                    errorMessage);
            }
            // Provided Python script does not contain a function named update_parameters
            catch (System.MissingMemberException) {
                throw new CompilationException(
                    Properties.Resources.ScriptMissingMemberErrorCaption,
                    Properties.Resources.ScriptMissingMemberError);
            }
            // some other problem occured while trying to execute the script
            catch (Exception ex) {
                throw new CompilationException(
                    Properties.Resources.ScriptGenericError,
                    ex.Message);
            }
        }
    }
}
